package controllers;

import models.entities.UserEntity;
import models.exceptions.UserExistsException;
import models.helpers.UserHelper;
import models.repository.UserRepositorySQL;
import models.repository.common.UserRepository;
import play.Play;
import play.data.validation.Email;
import play.data.validation.MinSize;
import play.data.validation.Required;
import play.data.validation.Validation;
import play.libs.Crypto;
import play.mvc.Before;
import play.mvc.Controller;
import play.mvc.Http;

import java.text.MessageFormat;

public class Secure extends Controller {
    private static UserRepository repository = new UserRepositorySQL();

    @Before(unless = {"login", "authenticate", "logout", "registration", "register"})
    static void checkAccess() throws Throwable {
        // Authent
        if (!session.contains("username")) {
            login(request.method.equals("GET") ? request.url : "/");
        }
        // Checks
        Check check = getActionAnnotation(Check.class);
        if (check != null) {
            check(check);
        }
        check = getControllerInheritedAnnotation(Check.class);
        if (check != null) {
            check(check);
        }
    }

    private static void check(Check check) throws Throwable {
        for (String role : check.value()) {
            if (role.trim().toLowerCase() == "admin") {
                boolean isRootAdmin = Play.configuration.getProperty("secure.rootAdmin").toLowerCase().trim() == session.get("username").toLowerCase().trim();
                if (isRootAdmin) {
                    return;
                }
            }
            models.shop.User user = UserHelper.getUser(session);
            boolean inRole = user.isInRole(role);
            if (!inRole) {
                forbidden();
            }
        }
    }

    public static void register(@Required @Email String email, @Required @MinSize(3) String password) throws Throwable {
        if (Validation.hasErrors()) {
            params.flash();
            Validation.keep();
            registration();
        } else {
            try {
                repository.register(new UserEntity(email, password, session.getId()));
                login(null);
            } catch (UserExistsException ex) {
                Validation.addError("email", "User with same email already exists");
                params.flash();
                Validation.keep();
                registration();
            }
        }
    }

    public static void registration() {
        render();
    }
    // ~~~ Login

    public static void login(String returnUrl) throws Throwable {
        Http.Cookie remember = request.cookies.get("rememberme");
        if (remember != null && remember.value.indexOf("-") > 0) {
            String sign = remember.value.substring(0, remember.value.indexOf("-"));
            String username = remember.value.substring(remember.value.indexOf("-") + 1);
            if (Crypto.sign(username).equals(sign)) {
                session.put("username", username);
                redirectToOriginalURL(returnUrl);
            }
        }
        flash.keep("url");
        render();
    }

    public static void authenticate(@Required @Email String username, @Required @MinSize(3) String password, boolean remember, String returnUrl) throws Throwable {
        if (Validation.hasErrors()) {
            redirectToLoginPageWithErrorMessage(returnUrl);
        } else {
            Boolean isAuthValid = repository.validate(new UserEntity(username, password, session.getId()));
            UserEntity authenticatedUser;
            if (!isAuthValid) {
                authenticatedUser = repository.getUser(username);
                if (authenticatedUser != null) {
                    notifiers.Mails.sendPasswordForExistentUser(authenticatedUser);
                    redirectToLoginPageWithErrorMessage(returnUrl);
                } else {
                    repository.register(new UserEntity(username, password, session.getId()));
                    authenticateUser(username, remember, returnUrl);
                }
            } else {
                authenticateUser(username, remember, returnUrl);
            }
        }
    }

    private static void authenticateUser(String username, boolean remember, String returnUrl) throws Throwable {
        UserEntity authenticatedUser = repository.getUser(username);
        UserEntity anonymousUser = repository.getAnonymousUser(session.getId());
        repository.mergeShoppingCarts(authenticatedUser.id, anonymousUser.id);
        play.cache.Cache.replace(MessageFormat.format("{0}_user", session.getId()), authenticatedUser, "1h");
        session.put("username", username);
        if (remember) {
            response.setCookie("rememberme", Crypto.sign(username) + "-" + username, "30d");
        }
        redirectToOriginalURL(returnUrl);
    }

    private static void redirectToLoginPageWithErrorMessage(String returnUrl) throws Throwable {
        flash.keep("url");
        flash.error("secure.error");
        params.flash();
        login(returnUrl);
    }

    public static void logout() throws Throwable {
        session.clear();
        response.removeCookie("rememberme");
        flash.success("secure.logout");
        login(null);
    }

    // ~~~ Utils

    static void redirectToOriginalURL(String url) throws Throwable {
        if (url == null) {
            url = "/";
        }
        redirect(url);
    }

}